package com.absbook.controls
{   
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.TimerEvent;
    import flash.ui.Keyboard;
    import flash.utils.Timer;
    
    import mx.controls.ComboBox;
    import mx.utils.ArrayUtil;
    
    /**
     * Extends the ComboBox to provide Firefox-like option selection
     * as you type. 
     * 
     * <p>The default functionality of the ComboBox is that
     * when you type any letter it selects just the first item in the
     * list that starts with that letter. The AutoFilterComboBox goes beyond
     * that allowing you to type a string to jump straight to an option
     * which starts with that string.</p>
     * 
     * <p>The AutoFilterComboBox will continue to update the selected item as you
     * type with the match getting more and more precise, it will stop when
     * there is no more items that match the input string.</p>
     * 
     * <p>With the AutoFilterComboBox the search string will be built upon as you 
     * type, but will reset if there is a period of typing in-activity (as 
     * defined by the <code>inactivityResetTimeout</code> property).</p>
     * 
     *  @mxml
     *
     *  <p>The <code>&lt;mx:AutoFilterComboBox&gt;</code> tag inherits all the tag attributes
     *  of its superclass, and adds the following tag attributes:</p>
     *
     *  <pre>
     *  &lt;defusion:AutoFilterComboBox
     *    <b>Properties</b>
     *    inactivityResetTimeout="1000"
     * 
     *  /&gt;
     *  </pre>
     * 
     * @see mx.controls.ComboBox
     */ 
    public class AutoFilterComboBox extends ComboBox
    {
        
        //--------------------------------------------------------------------------
        //
        //  Constructor
        //
        //--------------------------------------------------------------------------        
        
        public function AutoFilterComboBox()
        {
            super();
            _searchString = '';
            _clearTimer = new Timer( 1000, 1 );
            _clearTimer.addEventListener( TimerEvent.TIMER_COMPLETE, clearTimerCompleteHandler );
        }
        
        //--------------------------------------------------------------------------
        //
        //  Variables
        //
        //--------------------------------------------------------------------------        
        /**
         * The string that is added to as the user types to use as the search string 
         */
        private var _searchString:String;
        
        /**
         * The timer that we use to clear the search string after a period of inactivity
         */
        private var _clearTimer:Timer;

        /**
         * Keys we don't want to be bothered about handling
         */
        private var _keysSuperHandles:Array = [ Keyboard.DOWN, Keyboard.UP, Keyboard.ESCAPE, Keyboard.ENTER, Keyboard.PAGE_UP, Keyboard.PAGE_DOWN ];
    
        /**
         * Used to keep track of whether we're inside the keyDownHandler or not, this
         * allows us to stop the close() method from executing (default behaviour on
         * selecting an item) when we're selecting an item as type.
         */
        protected var _selectingItemAsTyping:Boolean = false;

        //--------------------------------------------------------------------------
        //
        //  Properties
        //
        //--------------------------------------------------------------------------        
        
        /**
         * The time, in milliseconds, of keyboard inactivity before the search string
         * is reset
         * 
         * @tiptext Time, in milliseconds, of keyboard inactivity before the search string is reset
         * @default 1000
         */ 
        public var inactivityResetTimeout:int = 1000;
        
        //--------------------------------------------------------------------------
        //
        //  Methods
        //
        //--------------------------------------------------------------------------        
        
        /**
         * Override the close function to take account of us changing
         * the selected item (see keyDownHandler & _foxyInKeyDown)
         * 
         * @see mx.controls.ComboBox::close()
         */
        override public function close(trigger:Event = null):void
        {
            if( !_selectingItemAsTyping ) {
                super.close();
            }
        }
        
        //--------------------------------------------------------------------------
        //
        //  Overridden event handlers
        //
        //--------------------------------------------------------------------------

        override protected function keyDownHandler(event:KeyboardEvent):void
        {
            var tmpCode:int = event.keyCode;        
            /*
             * Note: In an ideal world the best place to do this work would be within a sub class
             * of the List, but for some reason if the user doesn't open the drop down and
             * starts typing (e.g. tabs to the ComboBox and starts typing) then the ComboBox
             * keeps getting a new List from the factory. This stops us from keeping any sort
             * of state within our List sub class, and having the feature only when the user
             * has opened the drop down AND typed while it's open is not really very nice.
             *
             * So rather than going into overkill, by sub classing a few things and overriding 
             * far much more than I would like to, this compromise seemed the best option.           
             */         
            if( ArrayUtil.getItemIndex( tmpCode, _keysSuperHandles ) != -1 ) {
                // it's one of the keys we don't want to handle, let the parent handle it
                super.keyDownHandler( event );
            } else if(
                ( tmpCode >= 33 && tmpCode <= 126 ) 
                || tmpCode == Keyboard.SPACE
            ){
                // Internally the ComboBox uses a private variable called
                // bInKeyDown to make sure not to close the dropdown (if open)
                // in this situation (where we have changed the selected item
                // ourselves), we have to do something similar with a variable
                // we have access to (see the overridden close() method for more details)
                _selectingItemAsTyping = true;

                // it's one of the keys we want to handle
                _searchString += String.fromCharCode( tmpCode );
                
                // backup the selected index incase the findString doesn't find any matches
                var prevSelectedIndex:int = dropdown.selectedIndex; 
                
                // reset the selectedIndex stops from cycling through values as your typing, e.g.
                // if there were the values united kingdom and united states then while typing united 
                // the selected item would cycle between the two (which isn't very attractive)
                dropdown.selectedIndex = -1;                
                var matchedString:Boolean = dropdown.findString( _searchString );
                
                if( !matchedString ) {
                    // if we didn't find a match we put the selection back to where it was
                    // as it was us that removed the selection (when we set it to -1 above)
                    dropdown.selectedIndex = prevSelectedIndex;
                }
      
                // kick off the clear timer
                _clearTimer.reset();
                _clearTimer.start();
                
                _selectingItemAsTyping = false;
            } else {
                /*
                    it's another key that we don't care about (but not one we've explicitally
                    said we don't care about), so let the parent handle it
                */
                super.keyDownHandler( event );
            }
        }
        
        //--------------------------------------------------------------------------
        //
        //  Event handlers
        //
        //--------------------------------------------------------------------------        
        
        private function clearTimerCompleteHandler( event:TimerEvent ):void
        {
            this._searchString = '';
        }
        
    }
}